package vip.liux.backend.application.serviceImpl.pay;


import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import vip.liux.application.dots.PagedResultDto;
import vip.liux.application.dots.PagedResultRequestDto;
import vip.liux.backend.application.service.pay.*;
import vip.liux.backend.application.service.pay.dto.order.PayOrderCreateDto;
import vip.liux.backend.application.service.pay.dto.order.PayOrderDto;
import vip.liux.backend.application.service.pay.dto.refund.PayRefundCreateDto;
import vip.liux.backend.application.service.pay.dto.refund.PayRefundDetailsDto;
import vip.liux.backend.application.service.pay.dto.wallet.PayWalletDto;
import vip.liux.backend.application.service.pay.dto.wallet.PayWalletRechargeDto;
import vip.liux.contracts.models.pay.PayWalletRecharge;
import vip.liux.contracts.repositories.pay.PayWalletRechargeRepository;
import vip.liux.contracts.shared.enums.pay.PayOrderStatus;
import vip.liux.contracts.shared.enums.pay.PayRefundStatus;
import vip.liux.contracts.shared.enums.pay.PayWalletBizType;
import vip.liux.contracts.shared.kits.ApplicationKit;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;

import static org.apache.commons.lang3.ObjectUtils.notEqual;
import static vip.liux.application.PageKit.toPage;
import static vip.liux.application.PageKit.toPaged;
import static vip.liux.contracts.shared.enums.ErrorCode.*;
import static vip.liux.contracts.shared.util.ServiceExceptionUtil.exception;
import static vip.liux.core.utils.JsonUtils.toJson;


/**
 * 钱包充值 Service 实现类
 *
 * @author jason
 */
@Service
@Slf4j
public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {

    public static final String APP_KEY = "wallet-pay-app-key";
    private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值";
    @Resource
    private PayWalletRechargeRepository walletRechargeMapper;
    @Resource
    private PayWalletService payWalletService;
    @Resource
    private PayOrderService payOrderService;
    @Resource
    private PayRefundService payRefundService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayWalletRechargeDto createWalletRecharge(String uid, String userIp, BigDecimal payPrice) {
        // 1.1 计算充值金额
        // 1.2 插入充值记录
        PayWalletDto wallet = payWalletService.getOrCreateWallet(uid);
        PayWalletRecharge recharge = new PayWalletRecharge()
                .setWalletId(wallet.getId()).setPayPrice(payPrice).setBonusPrice(BigDecimal.ZERO)
                .setTotalPrice(payPrice);
        walletRechargeMapper.save(recharge);

        // 2.1 创建支付单
        String payOrderNo = payOrderService.createOrder(new PayOrderCreateDto()
                .setAppKey(APP_KEY).setUserIp(userIp)
                .setMerchantOrderId(Objects.requireNonNull(recharge.getId()).toString()) // 业务的订单编号
                .setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("")
                .setPrice(recharge.getPayPrice())
                .setExpireTime(LocalDateTime.now().plus(Duration.ofHours(2L)))); // TODO @芋艿：支付超时时间
        // 2.2 更新钱包充值记录中支付订单
        recharge.setPayOrderNo(payOrderNo);
        walletRechargeMapper.save(recharge);
        return AutoMapperProfile.INSTANCE.convert(recharge);
    }

    @Override
    public PagedResultDto<PayWalletRechargeDto> getWalletRechargePackagePage(String uid, PagedResultRequestDto pageReqVO, Boolean payStatus) {
        PayWalletDto wallet = payWalletService.getOrCreateWallet(uid);
        return toPaged(walletRechargeMapper.findByIdAndPayStatus(wallet.getId(), payStatus, toPage(pageReqVO)).map(AutoMapperProfile.INSTANCE::convert));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateWalletRechargerPaid(Long id, String payOrderNo) {
        // 1.1 校验钱包充值是否存在
        PayWalletRecharge recharge = walletRechargeMapper.findById(id).orElseThrow(() -> {
            log.error("[updateWalletRechargerPaid][recharge({}) payOrder({}) 不存在充值订单，请进行处理！]", id, payOrderNo);
            return exception(WALLET_RECHARGE_NOT_FOUND);

        });

        // 1.2 校验钱包充值是否可以支付
        if (recharge.getPayStatus()) {
            // 特殊：支付单号相同，直接返回，说明重复回调
            if (Objects.equals(recharge.getPayOrderNo(), payOrderNo)) {
                log.warn("[updateWalletRechargerPaid][recharge({}) 已支付，且支付单号相同({})，直接返回]", recharge, payOrderNo);
                return;
            }
            // 异常：支付单号不同，说明支付单号错误
            log.error("[updateWalletRechargerPaid][recharge({}) 已支付，但是支付单号不同({})，请进行处理！]", recharge, payOrderNo);
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
        }

        // 2. 校验支付订单的合法性
        PayOrderDto payOrderDO = validatePayOrderPaid(recharge, payOrderNo);

        // 3. 更新钱包充值的支付状态
        int updateCount = walletRechargeMapper.updateByIdAndPaid(id, false, true, LocalDateTime.now(), payOrderDO.getChannelCode());
        if (updateCount == 0) {
            throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
        }

        // 4. 更新钱包余额
        // TODO @jason：这样的话，未来提现会不会把充值的，也提现走哈。类似先充 100，送 110；然后提现 110；
        // TODO 需要钱包中加个可提现余额
        payWalletService.addWalletBalance(recharge.getWalletId(), String.valueOf(id), PayWalletBizType.RECHARGE, recharge.getTotalPrice());

        // 5. 发送订阅消息
        getSelf().sendWalletRechargerPaidMessage(payOrderNo, recharge);
    }

    @Async
    public void sendWalletRechargerPaidMessage(String payOrderNo, PayWalletRecharge walletRecharge) {
        // 1. 获得会员钱包信息
        PayWalletDto wallet = payWalletService.getWallet(walletRecharge.getWalletId());
        // todo 2. 构建并发送模版消息
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refundWalletRecharge(Long id, String userIp) {
        // 1.1 获取钱包充值记录
        PayWalletRecharge walletRecharge = walletRechargeMapper.findById(id).orElseThrow(() -> {
            log.error("[refundWalletRecharge][钱包充值记录不存在，钱包充值记录 id({})]", id);
            return exception(WALLET_RECHARGE_NOT_FOUND);
        });
        // 1.2 校验钱包充值是否可以发起退款
        PayWalletDto wallet = validateWalletRechargeCanRefund(walletRecharge);

        // 2. 冻结退款的余额，暂时只处理赠送的余额也全部退回
        payWalletService.freezePrice(wallet.getId(), walletRecharge.getTotalPrice());

        // 3. 创建退款单
        String walletRechargeId = String.valueOf(id);
        String refundId = walletRechargeId + "-refund";
        String payRefundNo = payRefundService.createRefund(new PayRefundCreateDto()
                .setAppKey(APP_KEY)
                .setUserIp(userIp)
                .setMerchantOrderId(walletRechargeId)
                .setMerchantRefundId(refundId)
                .setReason("想退钱")
                .setPrice(walletRecharge.getPayPrice()));

        // 4. 更新充值记录退款单号
        if (walletRechargeMapper.updateRefundStatusAndPayRefundNoById(PayRefundStatus.WAITING, payRefundNo, id) < 1) {
            log.error("[refundWalletRecharge][更新钱包充值记录退款单号失败，钱包充值记录 id({})]", id);
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateWalletRechargeRefunded(Long id, Long refundId, String payRefundNo) {
        // 1.1 获取钱包充值记录
        // 说明：因为 id 和 refundId 是相同的，所以直接使用 id 查询即可！
        PayWalletRecharge walletRecharge = walletRechargeMapper.findById(id).orElseThrow(() -> {
            log.error("[updateWalletRechargerPaid][钱包充值记录不存在，钱包充值记录 id({})]", id);
            return exception(WALLET_RECHARGE_NOT_FOUND);
        });
        // 1.2 校验钱包充值是否可以更新已退款
        PayRefundDetailsDto payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundNo);

        // 情况一：退款成功
        if (PayRefundStatus.isSuccess(payRefund.getStatus())) {
            // 2.1 更新钱包余额
            payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id,
                    PayWalletBizType.RECHARGE_REFUND, walletRecharge.getTotalPrice());

            walletRechargeMapper.updateByIdAndRefundedSuccess(id, PayRefundStatus.WAITING, PayRefundStatus.SUCCESS, payRefund.getSuccessTime(), walletRecharge.getTotalPrice(), walletRecharge.getPayPrice(), walletRecharge.getBonusPrice());
        }
        // 情况二：退款失败
        else if (PayRefundStatus.isFailure(payRefund.getStatus())) {
            // 2.2 解冻余额
            payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice());

            walletRechargeMapper.updateByIdAndRefundedFailure(id, PayRefundStatus.WAITING, PayRefundStatus.FAILURE);
        }
    }

    private PayRefundDetailsDto validateWalletRechargeCanRefunded(PayWalletRecharge walletRecharge, String payRefundNo) {
        // 1. 校验退款订单匹配
        if (notEqual(walletRecharge.getPayRefundNo(), payRefundNo)) {
            log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({})，请进行处理！钱包充值的数据是：{}]",
                    walletRecharge.getId(), payRefundNo, toJson(walletRecharge));
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }

        // 2.1 校验退款订单
        PayRefundDetailsDto payRefund = payRefundService.getRefundByRefundNo(payRefundNo);
        if (payRefund == null) {
            log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundNo);
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
        }
        // 2.2 校验退款金额一致
        if (notEqual(payRefund.getRefundPrice(), walletRecharge.getPayPrice())) {
            log.error("[validateWalletRechargeCanRefunded][钱包({}) payRefund({}) 退款金额不匹配，请进行处理！钱包数据是：{}，payRefund 数据是：{}]",
                    walletRecharge.getId(), payRefundNo, toJson(walletRecharge), toJson(payRefund));
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
        }
        // 2.3 校验退款订单商户订单是否匹配
        if (notEqual(payRefund.getMerchantRefundId(), walletRecharge.getId().toString())) {
            log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({})，请进行处理！payRefund 数据是：{}]",
                    walletRecharge.getId(), payRefundNo, toJson(payRefund));
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
        }
        return payRefund;
    }

    private PayWalletDto validateWalletRechargeCanRefund(PayWalletRecharge walletRecharge) {
        // 校验充值订单是否支付
        if (!walletRecharge.getPayStatus()) {
            throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID);
        }
        // 校验充值订单是否已退款
        if (walletRecharge.getPayRefundNo() != null) {
            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED);
        }
        // 校验钱包余额是否足够
        PayWalletDto wallet = payWalletService.getWallet(walletRecharge.getWalletId());
        Assert.notNull(wallet, "用户钱包(" + wallet.getId() + ") 不存在");
        if (wallet.getBalance().compareTo(walletRecharge.getTotalPrice()) < 0) {
            throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH);
        }
        // TODO @芋艿：需要考虑下，赠送的金额，会不会导致提现超过；
        return wallet;
    }

    /**
     * 校验支付订单的合法性
     *
     * @param recharge   充值订单
     * @param payOrderNo 支付订单编号
     * @return 支付订单
     */
    private PayOrderDto validatePayOrderPaid(PayWalletRecharge recharge, String payOrderNo) {
        // 1. 校验支付单是否存在
        PayOrderDto payOrder = payOrderService.getByOrderNo(payOrderNo);
        if (payOrder == null) {
            log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 不存在，请进行处理！]",
                    recharge.getId(), payOrderNo);
            throw exception(PAY_ORDER_NOT_FOUND);
        }

        // 2.1 校验支付单已支付
        if (!PayOrderStatus.isSuccess(payOrder.getStatus())) {
            log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 未支付，请进行处理！payOrder 数据是：{}]",
                    recharge.getId(), payOrderNo, toJson(payOrder));
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS);
        }
        // 2.2 校验支付金额一致
        if (notEqual(payOrder.getPrice(), recharge.getPayPrice())) {
            log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 支付金额不匹配，请进行处理！钱包 数据是：{}，payOrder 数据是：{}]",
                    recharge.getId(), payOrderNo, toJson(recharge), toJson(payOrder));
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH);
        }
        // 2.3 校验支付订单的商户订单匹配
        if (notEqual(payOrder.getMerchantOrderId(), recharge.getId().toString())) {
            log.error("[validatePayOrderPaid][充值订单({}) 支付单不匹配({})，请进行处理！payOrder 数据是：{}]",
                    recharge.getId(), payOrderNo, toJson(payOrder));
            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
        }
        return payOrder;
    }


    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PayWalletRechargeServiceImpl getSelf() {
        return ApplicationKit.getBean(getClass());
    }
}
